#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

typedef struct {
  unsigned int x_count;
  unsigned int y_count;
  double *x;
  double *y;
  double *w;
} KohLayer;

KohLayer* createKohLayer(unsigned int x_count, unsigned int y_count) {
  KohLayer* kl = malloc(sizeof(KohLayer));
  kl->x_count = x_count;
  kl->y_count = y_count;
  kl->x = malloc(x_count*sizeof(double));
  kl->y = malloc(y_count*sizeof(double));
  kl->w = malloc(x_count*y_count*sizeof(double));
  return kl;
};

void freeKohLayer(KohLayer* kl) {
  if(kl) {
    if(kl->x) free(kl->x);
    if(kl->y) free(kl->y);
    if(kl->w) free(kl->w);
    free(kl);
  };
};

void printX(KohLayer* kl, unsigned int size) {
  printf("\nLayer X\n");
  for(unsigned int i=0; i<kl->x_count; i++) {
    if(i % size == 0)
      printf("\n");
    printf("%f ", kl->x[i]);
  };
  printf("\n");
};

void printY(KohLayer* kl, unsigned int size) {
  printf("\nLayer Y\n");
  for(unsigned int i=0; i<kl->y_count; i++) {
    if(i % size == 0)
      printf("\n");
    printf("%f ", kl->y[i]);
  };
  printf("\n");
};

void printW(KohLayer* kl, unsigned int size) {
  for(unsigned int i=0; i<kl->y_count; i++) {
    for(unsigned int j=0; j<kl->x_count; j++) {
      if(j % size == 0)
        printf("\n");
      printf("%f ", kl->w[j*kl->y_count + i]);
    };
    printf("\n");
  };
};

void calcY(KohLayer* kl) {
  for(unsigned int i=0; i<kl->y_count; i++) {
    double sum = 0.0f;
    for(unsigned int j=0; j<kl->x_count; j++)
      sum += kl->x[j]*kl->w[j*kl->y_count + i];
    kl->y[i] = sum;
  };
};

void normalizeVector(double* v, unsigned int size) {
  double sum = 0.0f;
  for(unsigned int i=0; i<size; i++)
    sum += v[i]*v[i];
  sum = sqrt(sum);
  for(unsigned int i=0; i<size; i++)
    v[i] = v[i]/sum;
};

void initWeights(KohLayer* kl) {
  for(unsigned int i=0; i<kl->x_count; i++)
    for(unsigned int j=0; j<kl->y_count; j++)
      kl->w[i*kl->y_count + j] = 1.0f/kl->x_count;
};

void setX(double* v, KohLayer* kl) {
  memcpy(kl->x, v, sizeof(double)*kl->x_count);
};

void learnKLayer(KohLayer* kl, double alpha) {
  double mind = 100000.0f;
  double d;
  double b = 1.0f/kl->x_count;
  unsigned int mini = 0;
  for(unsigned int i=0; i<kl->y_count; i++) {
    d = 0.0f;
    for(unsigned int j=0; j<kl->x_count; j++)
      d += (alpha*kl->x[j]+(1.0f-alpha)*b - kl->w[j*kl->y_count + i])*
        (alpha*kl->x[j]+(1.0f-alpha)*b - kl->w[j*kl->y_count + i]);
    d = sqrt(d);
    if(d < mind) {
      mind = d;
      mini = i;
    };
  };

  for(unsigned int j=0; j<kl->x_count; j++)
    kl->w[j*kl->y_count + mini] += (kl->x[j] - kl->w[j*kl->y_count + mini])*0.7f;

};


int main(int argc, char* argv[]) {
  if(argc <= 1) {
    printf("%s <conf_file.txt>\n", argv[0]);
    return 0;
  };

  FILE* fp = fopen(argv[1], "r");
  if(!fp) {
    printf("Can't open file '%s'\n", argv[1]);
    return 0;
  };

  unsigned int x_count, y_count;
  fscanf(fp, "%d %d", &x_count, &y_count);
  printf("Layer x: %d  y: %d\n", x_count, y_count);

  KohLayer* kl = createKohLayer(x_count, y_count);

  unsigned int samples_count;
  fscanf(fp, "%d", &samples_count);
  printf("Samples count: %d\n", samples_count);

  double* samples = malloc(samples_count*sizeof(double)*kl->x_count);
  for(unsigned int i=0; i<samples_count*kl->x_count; i++) {
    float t;
    fscanf(fp, "%f", &t);
    samples[i] = t;
  };

  fclose(fp);

  for(unsigned int i=0; i<samples_count; i++)
    normalizeVector(&samples[i*kl->x_count], kl->x_count);

  initWeights(kl);
  for(int epochs=0; epochs<1000; epochs++) {
    for(unsigned int si=0; si<samples_count; si++) {
      setX(&samples[si*kl->x_count], kl);
      learnKLayer(kl, 1.0f*(epochs+1)/1000.0f);
    };
  };

  for(int epochs=0; epochs<1000; epochs++) {
    for(unsigned int si=0; si<samples_count; si++) {
      setX(&samples[si*kl->x_count], kl);
      learnKLayer(kl, 1.0f);
    };
  };


  printf("\n --- results --- \n");
  for(unsigned int si=0; si<samples_count; si++) {
    setX(&samples[si*kl->x_count], kl);
    calcY(kl);
    printX(kl, sqrt(kl->x_count));
    printY(kl, 5);
  };

  printW(kl, sqrt(kl->x_count));
  freeKohLayer(kl);
  free(samples);

  return 0;
};
